Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Add SetRuntimeEnvironment call for NI-DCPower #962

Conversation

ni-jfitzger
Copy link
Contributor

@ni-jfitzger ni-jfitzger commented Jul 17, 2023

What does this Pull Request accomplish?

Add an NiDCPowerLibrary::SetRuntimeEnvironment call upon library load, before init is called.

Implemented via the following changes:

  • Update source/codegen/metadata/nidcpower/functions_addon.py to privately add the SetRuntimeEnvironment function
  • Update source/codegen/metadata/nidcpower/__init__.py so that the new metadata will be merged. I merged metadata for attributes, functions and enums. I didn't mess with config_addon.py, though.
  • Update library.cpp.mako to call SetRuntimeEnvironment if the function exists
  • Generate a version.h with some of the values found in version.rc, defined as string constants (couldn't figure out any other way to get access to the data easily)
  • Update CMakeLists.txt and generate

Why should this Pull Request be merged?

As developers, we want to know how much nigrpc_device_server is used and which versions.

What testing has been done?

  • Built ni_grpc_device_server.exe locally and called it with the nimi-python system tests in CEIP test mode.
    • Verified that the Runtime Environment data was logged correctly.
  • Manually launched the server and called it, opening and closing sessions, as well as the python process that I called it from, multiple times. Confirmed that data was only logged once. This makes sense because the service that loads the library is supposed to remain loaded for the lifetime of the server instance.

TODOs:

@ni-jfitzger
Copy link
Contributor Author

TODO: update tests? Maybe testing should be done with nifake?

@@ -44,6 +45,10 @@ ${service_class_prefix}Library::${service_class_prefix}Library() : shared_librar
%>\
function_pointers_.${method_name} = reinterpret_cast<${method_name}Ptr>(shared_library_.get_function_pointer("${c_name}"));
% endfor
% if 'SetRuntimeEnvironment' in service_helpers.filter_api_functions(functions, only_mockable_functions=False):

this->SetRuntimeEnvironment(nidevice_grpc::NIDEVICE_GRPC_ORIGINALFILENAME.c_str(), nidevice_grpc::NIDEVICE_GRPC_FILEVERSION.c_str(), "", "");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I need to do a try, catch in case this errors?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than adding specific logic for a specific function, it might be better to define this requirement in the metadata and use that to add any extra "initialization functions" so that this is extensible to other functions in the future. I'll defer to @reckenro or @astarche on that since they know more about the code generation, though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't Python where you ask for forgiveness, not for permission :)
Best not to throw unless an error truly happened.

I think ideally, you have a well documented function that only calls into the driver if the function is indeed present. That means you don't rely on the codegenerated wrapper as-is.

I don't know how accessible the things you would need are. If they aren't then maybe it's fine to try/catch, not the end of the world.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like Marcos, I was expecting something like:

  if (function_pointers_.SetRuntimeEnvironment) {
    function_pointers_.SetRuntimeEnvironment(environment, environmentVersion, reserved1, reserved2);
  }

But I agree that try/catch is not the end of the world.

I'm always a fan of adding metadata conventions. But I think there's already a strong convention in the drivers to implement/use SetRuntimeEnvironment in this way and that it's implemented similarly in nimi-python so I'm happy for the team to use their judgement on it.

Is it possible that we'd want to automatically add SetRuntimeEnvironment even if drivers don't declare it in their metadata? If the convention is strong enough, it can be better to have it be global.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually just added that check.
Let me know if you think I need to document the reason for the call.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than adding specific logic for a specific function, it might be better to define this requirement in the metadata and use that to add any extra "initialization functions" so that this is extensible to other functions in the future. I'll defer to @reckenro or @astarche on that since they know more about the code generation, though.

I'll stick with the YAGNI (You Ain't Gonna Need It) principle and avoid overcomplicating it, since I haven't been told other wise.

@ni-jfitzger
Copy link
Contributor Author

I'm not sure why the validate_python check failed. I don't think it's related to my changes.

@@ -44,6 +45,10 @@ ${service_class_prefix}Library::${service_class_prefix}Library() : shared_librar
%>\
function_pointers_.${method_name} = reinterpret_cast<${method_name}Ptr>(shared_library_.get_function_pointer("${c_name}"));
% endfor
% if 'SetRuntimeEnvironment' in service_helpers.filter_api_functions(functions, only_mockable_functions=False):

this->SetRuntimeEnvironment(nidevice_grpc::NIDEVICE_GRPC_ORIGINALFILENAME.c_str(), nidevice_grpc::NIDEVICE_GRPC_FILEVERSION.c_str(), "", "");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than adding specific logic for a specific function, it might be better to define this requirement in the metadata and use that to add any extra "initialization functions" so that this is extensible to other functions in the future. I'll defer to @reckenro or @astarche on that since they know more about the code generation, though.

CMakeLists.txt Show resolved Hide resolved
@bkeryan
Copy link
Contributor

bkeryan commented Jul 17, 2023

I'm not sure why the validate_python check failed. I don't think it's related to my changes.

black 23.7 was released a week ago and it dropped Python 3.7 support.

validate_examples.py generates temporary Poetry projects on the fly, which is kind of unusual. It sounds like poetry add (at least in Poetry 1.2.2) decides to depend on black = "^23.7.0" before it realizes this won't work with Python 3.7. The easiest solution is probably to upgrade the Python version for the GitHub workflow to 3.8 or later.

Cc: @mshafer-NI

generated/version.h Outdated Show resolved Hide resolved
generated/nidcpower/nidcpower_library.h Show resolved Hide resolved
source/server/version.h.in Outdated Show resolved Hide resolved
source/server/version.h.in Outdated Show resolved Hide resolved
source/server/version.h.in Outdated Show resolved Hide resolved
generated/nidcpower/nidcpower_library.cpp Show resolved Hide resolved
@@ -44,6 +45,10 @@ ${service_class_prefix}Library::${service_class_prefix}Library() : shared_librar
%>\
function_pointers_.${method_name} = reinterpret_cast<${method_name}Ptr>(shared_library_.get_function_pointer("${c_name}"));
% endfor
% if 'SetRuntimeEnvironment' in service_helpers.filter_api_functions(functions, only_mockable_functions=False):

this->SetRuntimeEnvironment(nidevice_grpc::NIDEVICE_GRPC_ORIGINALFILENAME.c_str(), nidevice_grpc::NIDEVICE_GRPC_FILEVERSION.c_str(), "", "");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't Python where you ask for forgiveness, not for permission :)
Best not to throw unless an error truly happened.

I think ideally, you have a well documented function that only calls into the driver if the function is indeed present. That means you don't rely on the codegenerated wrapper as-is.

I don't know how accessible the things you would need are. If they aren't then maybe it's fine to try/catch, not the end of the world.

@ni-jfitzger
Copy link
Contributor Author

I'm not sure why the validate_python check failed. I don't think it's related to my changes.

black 23.7 was released a week ago and it dropped Python 3.7 support.

validate_examples.py generates temporary Poetry projects on the fly, which is kind of unusual. It sounds like poetry add (at least in Poetry 1.2.2) decides to depend on black = "^23.7.0" before it realizes this won't work with Python 3.7. The easiest solution is probably to upgrade the Python version for the GitHub workflow to 3.8 or later.

Cc: @mshafer-NI

Created #963 to address the issue.

* Use constexpr to avoid codebloat
* Follow naming conventions
* Eliminate unused constants
* Add comment to CMakeLists.txt
@ni-jfitzger
Copy link
Contributor Author

ni-jfitzger commented Jul 20, 2023

I've been thinking about how to test that SetRuntimeEnvironment is called when it should be and not called when it shouldn't be.

  • A mock of the NiFakeLibrary constructor (where the SetRuntimeEnvironment call happens) won't do anything except what the caller tells it to do.
  • If I move the SetRuntimeEnvironment call up a level into the function that calls the constructor, well that function creates the library object, itself, so I can't pass in a mock of the Library class.
  • The SharedLibrary object that loads the dll is constructed by the NiFakeLibrary constructor, so I can't pass in a fake or mock of SharedLibrary to noop the library loading.

@reckenro @astarche How would you feel about me tweaking the Library classes to have the SharedLibrary type passed to the constructor (so that a test could pass a mock or fake)?

@astarche
Copy link
Collaborator

@ni-jfitzger @reckenro Passing in the shared library sounds alright to me. I believe you could make it a unique_ptr and pass it in via make_unique. A shared_library abstraction is a reasonable place to introduce a pointer for testability.

Seems more likely that this code would fail because shared_library doesn't do what you expect rather than because your if statement doesn't do what you expect. But we haven't invested in building some dummy dll for testing shared_library so now is not the time to do it. We typically fall back to relying on driver-level acceptance tests, but in this case I guess we don't have a good way to test that either (both hard to verify the call and hard to find a driver that doesn't support it).

So overall: sounds good.

@ni-jfitzger
Copy link
Contributor Author

This PR is being abandonded in favor of #973

@bkeryan
Copy link
Contributor

bkeryan commented Jul 20, 2023

Passing in the shared library sounds alright to me. I believe you could make it a unique_ptr and pass it in via make_unique.

Note that the NifooLibrary constructor will move the test's MockSharedLibrary, invalidating it. shared_ptr may be simpler, especially if you need to access the mock after the NifooLibrary takes ownership.

But we haven't invested in building some dummy dll for testing shared_library so now is not the time to do it.

With mocking, you don't need a dummy DLL. You can set up the MockSharedLibrary to return pointers to functions implemented in the test EXE. However, it still seems like a relatively low value test.

We typically fall back to relying on driver-level acceptance tests, but in this case I guess we don't have a good way to test that either (both hard to verify the call and hard to find a driver that doesn't support it).

Is it hard to write a C++ test that logs ETW events? In C#, we have test utilities for writing tests that capture ETW events for CEIP or whatever.

@ni-jfitzger ni-jfitzger deleted the nidcpower-add-setRuntimeEnvironment-call branch August 7, 2023 14:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants